﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;

namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.WPF.Custom;
  using Bling.DX;

  public class ListManager : Canvas {
    private readonly List<TextBox> Labels = new List<TextBox>();
    private int Counter = 0;
    private IntBl Selected;
    public ListManager(CanvasBl Canvas) {
      this.Bl().CanvasParent = Canvas;
      this.Bl().Height = 0d;
      Selected = this.Property(-1.Bl());
      Unselect = IntBl.Assign(Selected, -1);
    }
    public readonly Action Unselect;

    public void AddAction(Action A) {
      var PermId = Labels.Count;
      var lbl = new TextBoxBl(this) {
        Font = { Brush = (Selected == PermId).Condition(Brushes.Red, Brushes.White.Bl()), Weight = FontWeights.SemiBold },
        Background = Brushes.Transparent,
        //IsReadOnly = true, 
        MaxLines = 1, Width = this.Bl().Width, Left = 0d,
        Text = "Action-" + Counter,
        
      };
      Counter += 1;
      var cover = new CanvasBl(this) {
        Size = lbl.Size, LeftTop = lbl.LeftTop,
        Background = Brushes.Transparent,
      };
      lbl.Top = (PermId) * lbl.Height;
      this.Bl().Height = PermId * lbl.Height;
      Labels.Add(lbl.CurrentValue);

      var Pos = this.Bl().MousePosition;

      new ComplexDragManager<Canvas, CanvasBl>(cover) {
        OnClick = E => {
          A();
          Selected.Now = PermId;
        },
        OnHold = E => {
          Keyboard.Focus(lbl.CurrentValue);
          lbl.CurrentValue.SelectAll();
        },
        StartDrag = E => {
          var CurrentIndex = Labels.IndexOf(lbl.CurrentValue);
          var bias = 0d; // (Pos.Y - (CurrentIndex * lbl.Height)).Now;
          var GetIndex = ((IntBl)((Pos.Y - bias) / lbl.Height)).AsFunc;
          return (E0, DoUpdate) => {
            if (!DoUpdate) return;
            var AtIndex0 = GetIndex();
            var AtIndex = AtIndex0;
            AtIndex = Math.Max(AtIndex, 0);
            AtIndex = Math.Min(AtIndex, Labels.Count - 1);
            CurrentIndex = Labels.IndexOf(lbl.CurrentValue);
            if (AtIndex != CurrentIndex) {
              Labels.RemoveAt(CurrentIndex); // this is self.
              if (AtIndex < CurrentIndex) {
                Labels.Insert(AtIndex, lbl.CurrentValue);
                for (int i = AtIndex; i < Labels.Count; i++)
                  Labels[i].Bl().Top = i * Labels[i].Bl().Height;
              } else {
                Labels.Insert(AtIndex, lbl.CurrentValue);
                for (int i = CurrentIndex; i < Labels.Count; i++)
                  Labels[i].Bl().Top = i * Labels[i].Bl().Height;

              }
            }
          };
        },
      };
    }
  }
  public class FakeVolume : HasHost {

    private PointBl CutEndFor(PointBl CutSlope, PointBl CutStart) {
      var p = new PointBl(CutSlope.X.Sign, CutSlope.Y.Sign);
      p = (p + 1d).Min(1d);
      var t = (p - CutStart) / CutSlope;
      return CutStart + t.X.Min(t.Y) * CutSlope;
    }
    public static readonly PointBl BufferSize = new PointBl(0, 0);
    public static readonly PointBl ViewportSize = new PointBl(500, 500) + BufferSize;
    private enum MouseMode {
      Rotate, Pan, Zoom
    }


    public FakeVolume()
      : base(true) {
      CanvasBl Canvas = this;
      Host.Size = ViewportSize - BufferSize;
      Host.CenterBottom = new PointBl(Canvas.CenterSize.X, Canvas.Height);
      OverlayContent.Background = new ColorBl(1, 1, 1, .01);

      Canvas.Background = Brushes.Black;
      // properties.
      var RotateA = this.Property(QuaternionBl.Identity);

      var AtCut = this.Property(QuaternionBl.Identity);
      //var AtCutInverse = //this.Property
      //  (AtCut.Invert /*.Matrix*/);

      var CutStart = this.Property(new PointBl(0, .5));
      var CutSlope = this.Property(new PointBl(1, 0).Normalize);
      var Pan = this.Property(PointBl.Default);
      var Zoom = this.Property(1d.Bl());
      DoubleBl[] MaterialScales = new DoubleBl[MaterialColors.Length];
      for (int i = 0; i < MaterialColors.Length; i++) MaterialScales[i] = this.Property(1d.Bl());

      // derived.
      QuaternionBl WorldRotate = null; // = RotateA * RotateB;
      DoubleBl CutSlide, CutAngle;
      var CutEnd = CutEndFor(CutSlope, CutStart);
      Action ResetCutSlide = () => { };
      {
        var CutSlideSlider = new SliderBl(Canvas) {
          CenterBottom = Host.CenterTop,
          Width = Host.Width,
          Minimum = 0,
          Maximum = 1,
          Value = 0,
        };
        CutSlide = CutSlideSlider;
        new LabelBl(Canvas) {
          RightCenter = CutSlideSlider.LeftCenter,
          Font = { Brush = Brushes.White, Weight = FontWeights.SemiBold, },
          Content = "Slide",
          Visibility = CutSlideSlider.Visibility,
        };
        // first, create some sliders to control the simulation.
        var CutAngleSlider = new SliderBl(Canvas) {
          CenterBottom = CutSlideSlider.CenterTop,
          Width = Host.Width,
          Minimum = 0,
          Maximum = 1,
          Value = 0
        };
        CutAngle = CutAngleSlider;
        var AllSaveProperties = new Brand[] {
          RotateA, AtCut, CutStart, CutSlope, Pan, Zoom, CutAngle
        };

        new LabelBl(Canvas) {
          RightCenter = CutAngleSlider.LeftCenter,
          Font = { Brush = Brushes.White, Weight = FontWeights.SemiBold, },
          Content = "Angle",
          Visibility = CutAngleSlider.Visibility,
        };

        var Waypoints = new ListManager(Canvas);
        {
          Waypoints.Bl().LeftTop = Host.RightTop;
          Waypoints.Bl().Width = 100d;
        }
        var SaveRestore = this.Property(DoubleBl.Default);

        ButtonBl Save = new ButtonBl(Canvas) {
          LeftBottom = Host.RightTop,
          Content = "Save Position",
          Width = 100,
          Click = () => {
            var SavePoint = new WayPoint(AllSaveProperties);
            Action a = () => {
              SavePoint.Restore(SaveRestore.Ease.Cubic.InOut);
              SaveRestore.Stop(false);
              SaveRestore.Now = 0d;
              SaveRestore.Animate().Duration(500d).Completed((d) => ResetCutSlide()).To = 1d;
            };
            Waypoints.AddAction(a);
          },
        };

        {
          // sliders
          var ScaleSlider = new SliderBl(Canvas) {
            LeftBottom = CutAngleSlider.LeftTop,
            Width = CutAngleSlider.Width,
            Minimum = 0,
            Maximum = 1d,
            Value = MaterialScales[0],
          };
          MaterialScales[0].Bind = ScaleSlider.Value;
          new LabelBl(Canvas) {
            RightCenter = ScaleSlider.LeftCenter,
            Font = { Brush = Brushes.White, Weight = FontWeights.SemiBold },
            Content = "Transfer",
          };

          RectangleBl[] Outers = new RectangleBl[MaterialColors.Length];
          var Pos = ScaleSlider.LeftTop;
          int SelectedScale = 0;
          for (int i = 0; i < MaterialColors.Length; i++) {
            var i0 = i;
            Outers[i0] = new RectangleBl(Canvas) {
              Size = 22,
              ZIndex = 0,
              Fill = { Now = (i0 == SelectedScale ? Brushes.White : Brushes.DarkGray) },
              Stroke = { Brush = Brushes.Transparent, Thickness = 1d },
              LeftBottom = Pos,
              OnMouseDown = (x, y) => {
                Outers[SelectedScale].Fill.Now = Brushes.DarkGray;
                MaterialScales[SelectedScale].Now = MaterialScales[SelectedScale];

                SelectedScale = i0;
                Outers[SelectedScale].Fill.Now = Brushes.White;
                ScaleSlider.Value.Now = MaterialScales[i0];
                MaterialScales[i0].ClearNow();
                MaterialScales[i0].Bind = ScaleSlider.Value;
                return true;
              },
            };
            new RectangleBl(Canvas) {
              Size = 9 + 9 * MaterialScales[i0],
              ZIndex = 1,
              IsHitTestVisible = false,
              Fill = MaterialColors[i0],
              CenterPosition = Outers[i0].CenterPosition,
            };
            Pos = Outers[i0].RightBottom;
          }
        }
      }
      {
        {
          var CutRect = new RectangleBl(OverlayContent) {
            LeftTop = 0d,
            Size = OverlayContent.Size,
            Stroke = { Brush = Brushes.White, Thickness = 6d, },
            Opacity = 0.3,
            ZIndex = 4,
          };
          var MouseStart = this.Property(new PointBl(0, 0));
          var InitCut = PointBl.Assign(MouseStart, OverlayContent.MousePosition);
          var MouseEnd = OverlayContent.MousePosition;
          Action UpdateCut;
          {
            var Start = MouseStart / ViewportSize;
            var End = MouseEnd / ViewportSize;

            Start += Pan / new PointBl(-2d, +2d);
            End += Pan / new PointBl(-2d, +2d);

            Start = Start * 2d - 1d;
            End = End * 2d - 1d;
            Start = Start / Zoom;
            End = End / Zoom;
            Start = (Start + 1d) / 2d;
            End = (End + 1d) / 2d;

            var Slope = (End - Start).Normalize;
            var Target = (new PointBl(Slope.X.Sign, Slope.Y.Sign));
            Target = (Target + 1d).Min(1d);
            Target = 1d - Target;
            var UseSlope = Slope * -1d; // look backward.
            var t = (Target - Start) / UseSlope;
            var pX = Start + t.X * UseSlope;
            var pY = Start + t.Y * UseSlope;
            var NewCutStart = (t.X < t.Y).Condition(pX, pY);
            var UpdateCutA = PointBl.Assign(CutStart, NewCutStart);
            var UpdateCutB = PointBl.Assign(CutSlope, Slope);

            UpdateCut = () => { UpdateCutA(); UpdateCutB(); };
          }
          ResetCutSlide = () => {
            BoolBl StartIsX = CutStart.X > 0 & CutStart.X < 1;
            BoolBl EndIsX = CutEnd.X > 0 & CutEnd.X < 1;
            var StartRatio = CutStart[StartIsX.Condition(0, 1)];
            var EndRatio = CutStart[StartIsX.Condition(0, 1)];
            var Average = (StartRatio + EndRatio) / 2d;
            CutSlide.Now = StartRatio;

            PointBl Slope = CutSlope.CurrentValue; // capture slow
            DoubleBl NewStartRatio = CutSlide;
            PointBl NewCutStart =
              ((BoolBl)StartIsX.CurrentValue).Condition(new PointBl(NewStartRatio, CutStart.Y.CurrentValue),
                                                         new PointBl(CutStart.X.CurrentValue, NewStartRatio));
            CutStart.ClearNow();
            CutStart.Bind = NewCutStart;

          };

          {
            CutRect.InstallDragHandler(() => {
              CutAngle.Now = CutAngle.Now.Max(.1d);
              AtCut.Now = WorldRotate.Now;
              InitCut();
              return true;
            }, () => {
              UpdateCut();
            }, () => {
              // figure out what kind of cut this is
              ResetCutSlide();
            });
          }
        }
      }
      DoubleBl SphereDiameter = 400d;
      EllipseBl sphere = new EllipseBl(OverlayContent) {
        Size = SphereDiameter,
        CenterPosition = OverlayContent.CenterSize,
        Fill = new ColorBl(1, 1, 1, 0.01),
        ZIndex = 10,
        Stroke = { Brush = Brushes.White, Thickness = 0 },
        IsHitTestVisible = false
      };
      {
        var MouseInit = this.Property(Point3DBl.Default);

        var MouseAt = sphere.MousePosition / sphere.Size;
        MouseAt = MouseAt * 2d - 1d;
        MouseAt = MouseAt * new PointBl(-1d, +1d); // flip X
        var MouseAt3D = new Point3DBl(MouseAt, (1d - MouseAt.LengthSquared).Max(0d).Sqrt);
        MouseAt3D = RotateA.Invert.Transform(MouseAt3D);
        {
          Action SetMouseInit = Point3DBl.Assign(MouseInit, MouseAt3D);
          var q = MouseAt3D.AngleBetween(MouseInit);
          //var qA = q;
          var Ctl = BoolBl.State(() => {
            return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
          });
          var RotateB = this.Property(QuaternionBl.Identity);
          WorldRotate = RotateA * RotateB;

          Action SetRotateB = QuaternionBl.Assign(RotateB, q);
          Action SetRotateA = QuaternionBl.Assign(RotateA, WorldRotate);
          Action ResetRotateB = QuaternionBl.Assign(RotateB, QuaternionBl.Identity);
          var PanInit = this.Property(PointBl.Default);
          var SetPanInit = PointBl.Assign(PanInit, MouseAt);
          var DoPan = PointBl.Assign(Pan, Pan + (MouseAt - PanInit) * -1d);

          OverlayContent.InstallDragHandler(() => {
            SetMouseInit();
            SetPanInit();
            return true;
          }, () => {
            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
              DoPan();
              SetPanInit();
            } else SetRotateB();
          }, () => {
            SetRotateA();
            ResetRotateB();
          });
          var UpdateZoom = DoubleBl.Assign<int>((unused) => Zoom, (sign, OldZoom) => (OldZoom + sign.Bl() * .1d.Bl()).Max(.1).Min(10));

          OverlayContent.CurrentValue.MouseWheel += (x, y) => {
            y.Handled = true;
            var sign = Math.Sign(y.Delta);
            UpdateZoom(sign);
          };

        }
      }


      // world matrix specifies where a render object will go in the world. Just set it to the default for now.

      int DIM;
      {
        var canvasD = new CanvasBl() {
          Width = 400d,
        };
        var dimSlider = new SliderBl(canvasD) {
          Minimum = 5,
          Maximum = 60,
          Value = 20,
          Width = canvasD.Width,
          LeftTop = 0d,
        };
        var button = new ButtonBl(canvasD) {
          LeftTop = dimSlider.LeftBottom,
          Content = "Close at DIM = " + ((IntBl)dimSlider.Value).ToStringBl(),
        };
        canvasD.Height = dimSlider.Height + button.Height;
        var dlg = new WindowBl() {
          Content = canvasD,
          SizeToContent = SizeToContent.WidthAndHeight,
          ShowInTaskbar = false,
        };
        button.Click = () => dlg.CurrentValue.Close();

        dlg.CurrentValue.ShowDialog();
        DIM = (int)dimSlider.Value.CurrentValue;
      }


      // represents where our eye is in the world.
      Point3DBl WorldCenter = new Point3DBl(0, 0, 0);

      // Configure a WPF perspective camera.

      //MatrixBl<D4, D4> CameraMatrix;

      Point3DBl CameraPosition = WorldCenter + new Point3DBl(0, 0, -8 /** (1d / Zoom)*/);
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = CameraPosition,
        Direction = { Look = WorldCenter },
        FieldOfView = .1.PI().AsDegrees,
      };
      //CameraMatrix = Camera.Matrix;
      CameraPosition = Camera.Position;


      // create a new device in which to hold rendering information
      // this will actually cause rendering to occur in a WPF host. 
      var lightA = new SpotLightBl() {
        Direction = new Point3DBl(0, 0, 1d).Normalize,
        Color = Colors.Pink,
        Position = new Point3DBl(0, 0, -10),
        InnerConeAngle = 2.ToDegrees(),
        OuterConeAngle = 4.ToDegrees(),
      };
      var lightB = new DirectionalLightBl() {
        Direction = new Point3DBl(-.5, -.5, 10d).Normalize,
        Color = Colors.White,
      };
      var light = lightA + lightB;
      var diffuse = new DiffuseMaterialCl() { Factor = .4, Ambient = { Factor = .4 } };
      var Cubish = new CubeTopology();


      {
        var SIZE = (1d / ((double)DIM - 0d));
        PointBl Slope;
        PointBl NewStart;
        {
          BoolBl DoSwap = (CutEnd.Y < CutStart.Y);
          NewStart = DoSwap.Condition(CutEnd, CutStart);
          var NewEnd = DoSwap.Condition(CutStart, CutEnd);
          Slope = (NewEnd - NewStart).Normalize;
        }
        Slope = (Slope * -1d); 
        NewStart = NewStart * 2d - 1d;
        var AxisSlope = Slope.XY();
        MatrixBl<D4, D4> ProjectView = (Camera.Project * Camera.View);
        MatrixBl<D4, D4> ZoomPanWorld = Zoom.XXX().Scale(new Point3DBl(Pan.XY(), -2)) *
              Pan.InsertZ(0).Translate() *
              WorldRotate.Matrix;

        Device.Render(Cubish.Count, DIM * DIM * DIM, (idx, jdx, vertex) => {
          IntBl x = (jdx % (DIM));
          IntBl y = (jdx / (DIM)) % DIM;
          IntBl z = (jdx / (DIM)) / DIM;
          Point3DBl xyz = new Point3DBl(x, y, z);
          var Translate = (xyz * (SIZE * 2)).Translate();
          MatrixBl<D4, D4> ScaleDown = SIZE.Bl().XXX().Scale((Point3DBl)(-1d));


          MatrixBl<D4, D4> CutMatrix = MatrixBl<D4, D4>.Identity;
          QuaternionBl CutRotate = QuaternionBl.Identity;
          {
            {
              var CutPos = (AtCut.Matrix * Translate * ScaleDown).Transform(0d.Bl().XXX());
              var X = NewStart.X - (CutPos.Y + NewStart.Y) / Slope.Y * Slope.X;
              var CutDir = (CutPos.X >= X).Condition(+1d.Bl(), -1d.Bl());
              {
                var p = new Point3DBl(AxisSlope.YX(), 0) * CutAngle * CutDir * -1d;
                CutMatrix = AtCut.Invert.Transform(p).Translate();
                {
                  var CutAngle0 = CutAngle.PI() * .25d * CutDir;

                  var CutRotateAxis = new Point3DBl(AxisSlope * new PointBl(-1, +1), 0d);
                  CutRotateAxis = AtCut.Invert.Transform(CutRotateAxis);
                  CutRotate = (new QuaternionBl(CutRotateAxis, CutAngle0));
                }
                CutMatrix = CutMatrix * CutRotate.Matrix;
              }
            }
          }
          Point3DBl WorldPos;
          IntBl MatIndex = new Bling.DSL.TableExpr2<int>(GetVoxels2(DIM), jdx);
          ColorBl clr = new Bling.DSL.TableExpr2<Color>(MaterialColors, MatIndex);
          DoubleBl TransferScale = 0d;
          for (int i = 0; i < MaterialScales.Length; i++) {
            TransferScale = TransferScale + MaterialScales[i] * (i == MatIndex).Condition(1d, 0d);
          }
          {
            var Total = ProjectView * ZoomPanWorld * CutMatrix * Translate * ScaleDown;
            Total = Total * TransferScale.XXX().Scale();
            Point4DBl pos = Total * Cubish[idx];
            vertex.Position = pos;
            WorldPos = (Point3DBl)pos;
          }
          // do not including any translation for normal!
          var normal = (WorldRotate * CutRotate).Transform(Cubish.Normal(idx / 4)).Normalize;
          clr = diffuse.ApplyLights(light + new AmbientLightBl() { Color = Colors.White }).
            ApplyAt(WorldPos, normal, CameraPosition).
            ForColor(clr);
          clr = new ColorBl(clr.ScRGB, TransferScale);
          vertex.Color = clr;
        }, Cubish.IndexBuffer);
      }
    }
    private Color[] GetVoxels(int DIM) {
      Color[] CubeColors = new Color[DIM * DIM * DIM];
      {
        var rand = new Random();
        Action<int, double, double, Func<Color, double, Color>> G = (Count, Min, Max, Fc) => {
          for (int i = 0; i < Count; i++) {
            // try to find a coordinate.
            int x = rand.Next(DIM);
            int y = rand.Next(DIM);
            int z = rand.Next(DIM);
            var sz = Min + (Max - Min) * rand.NextDouble();
            for (int xi = 0; true; xi++) {
              var dxi = ((double)xi) / ((double)DIM);
              if (dxi > sz) break;
              for (int yi = 0; true; yi++) {
                var dyi = ((double)yi) / ((double)DIM);
                if (dyi > sz) break;
                for (int zi = 0; true; zi++) {
                  var dzi = ((double)zi) / ((double)DIM);
                  if (dzi > sz) break;

                  var v = new Vector3D(dxi, dyi, dzi);
                  var length = 1d - v.Length / sz;
                  if (length <= 0) continue;
                  Action<int, int, int> F = (xj, yj, zj) => {
                    if (xj >= DIM || xj < 0) return;
                    if (yj >= DIM || yj < 0) return;
                    if (zj >= DIM || zj < 0) return;
                    var jdx = xj + yj * DIM + zj * DIM * DIM;
                    if (CubeColors[jdx] == null) CubeColors[jdx] = Color.FromScRgb(1, 0, 0, 0);
                    var clr = CubeColors[jdx];
                    clr = Fc(clr, length);
                    CubeColors[jdx] = clr;
                  };
                  F(x + xi, y + yi, z + zi);
                  F(x + xi, y + yi, z - zi);
                  F(x + xi, y - yi, z + zi);
                  F(x + xi, y - yi, z - zi);

                  F(x - xi, y + yi, z + zi);
                  F(x - xi, y + yi, z - zi);
                  F(x - xi, y - yi, z + zi);
                  F(x - xi, y - yi, z - zi);
                }
              }
            }
          };
        };
        G(50, .03, .15, (clr, len) => {
          clr.ScB = (float)len;
          return clr;
        });
        G(10, .1, .2, (clr, len) => {
          var total = len + clr.ScB;
          clr.ScG = (float)(len / total);
          return clr;
        });
        for (int xi = 0; xi < DIM; xi++) {
          for (int yi = 0; yi < DIM; yi++) {
            for (int zi = 0; zi < DIM; zi++) {
              var jdx = xi + yi * DIM + zi * DIM * DIM;
              var clr = CubeColors[jdx];
              if (clr == null) CubeColors[jdx] = Colors.Red;
              else {
                var total = Math.Min(clr.ScB + clr.ScG, 1d);
                clr.ScR = (float)(1d - total);
                CubeColors[jdx] = clr;
              }
            }
          }
        }
      }

      return CubeColors;
    }
    private int[] GetVoxels2(int DIM) {
      int[] CubeColors = new int[DIM * DIM * DIM];
      {
        var rand = new Random();
        Action<int, double, double, Func<int, double, int>> G = (Count, Min, Max, Fc) => {
          for (int i = 0; i < Count; i++) {
            // try to find a coordinate.
            int x = rand.Next(DIM);
            int y = rand.Next(DIM);
            int z = rand.Next(DIM);
            var sz = Min + (Max - Min) * rand.NextDouble();
            for (int xi = 0; true; xi++) {
              var dxi = ((double)xi) / ((double)DIM);
              if (dxi > sz) break;
              for (int yi = 0; true; yi++) {
                var dyi = ((double)yi) / ((double)DIM);
                if (dyi > sz) break;
                for (int zi = 0; true; zi++) {
                  var dzi = ((double)zi) / ((double)DIM);
                  if (dzi > sz) break;

                  var v = new Vector3D(dxi, dyi, dzi);
                  var length = 1d - v.Length / sz;
                  if (length <= 0) continue;
                  Action<int, int, int> F = (xj, yj, zj) => {
                    if (xj >= DIM || xj < 0) return;
                    if (yj >= DIM || yj < 0) return;
                    if (zj >= DIM || zj < 0) return;
                    var jdx = xj + yj * DIM + zj * DIM * DIM;
                    var clr = CubeColors[jdx];
                    clr = Fc(clr, length);
                    CubeColors[jdx] = clr;
                  };
                  F(x + xi, y + yi, z + zi);
                  F(x + xi, y + yi, z - zi);
                  F(x + xi, y - yi, z + zi);
                  F(x + xi, y - yi, z - zi);

                  F(x - xi, y + yi, z + zi);
                  F(x - xi, y + yi, z - zi);
                  F(x - xi, y - yi, z + zi);
                  F(x - xi, y - yi, z - zi);
                }
              }
            }
          };
        };
        G(5 , .2, .4, (clr, len) => 1);
        G(10, .1, .2, (clr, len) => 2);
        G(50, .03, .15, (clr, len) => 3);
        G(50, .03, .15, (clr, len) => 4);
      }
      return CubeColors;
    }
    public static readonly Color[] MaterialColors = new Color[] {
      Colors.Wheat, 
      Colors.Orange,
      Colors.DarkOrange,
      Colors.Salmon,
      Colors.Purple,
    };
  }
}


/*
        if (false) {
          Func<PointBl, DoubleBl, PointBl> F = (p, z) => {
            p = (CutTransform).To3D(p.InsertZ(z)).XY();
            p = p * ViewportSize;
            return p;
          };
          Func<PointBl, DoubleBl, PointBl> G = (p, z) => {
            p = p / ViewportSize;
            p = (CutTransform).To2D(p.InsertZ(z));
            return p;
          };

          new Bling.Shapes.PathShapeBl(OverlayContent) {
            ZIndex = 2,
            IsHitTestVisible = false,
            Visibility = true,
            Stroke = { Brush = Brushes.Orange, Thickness = 4 },
            Opacity = 0.75,
            Start = F(CutStart, -1),
            Segments = {
              AddMany = new PathSegmentBl[] {
                new LineSegmentBl() { Point = F(CutEnd, -1), },
                new LineSegmentBl() { Point = F(CutEnd, +1), },
                new LineSegmentBl() { Point = F(CutStart, +1), },
                new LineSegmentBl() { Point = F(CutStart, -1), },
              },
            },
          };

          {
            Func<PointBl, PointBl, DoubleBl, PointBl> ComputeEndPoint = (FromA, FromB, Z) => {
              var MousePosition = G(OverlayContent.MousePosition, Z);
              var Delta = MousePosition - FromA;
              var q = FromB + Delta;
              q = q.Saturate;
              q = 2d * q - 1d;
              var h = q.Abs;
              q = (h.X > h.Y).Condition(new PointBl(q.X.Sign, q.Y), new PointBl(q.X, q.Y.Sign));
              q = (q + 1d) / 2d;
              return q;
            };
            Action<Point3DBl, Action, BrushBl> MakeControl = (From3D, Refresh, Fill) => {
              new EllipseBl(OverlayContent) { // scroll. 
                CenterPosition = From3D.XY() * ViewportSize,
                Size = 20d,
                Opacity = 0.5,
                ZIndex = 11,
                Fill = Fill,
                OnMouseDown = (ellipse, y) => {
                  y.Handled = true;
                  Mouse.Capture(ellipse.CurrentValue, CaptureMode.Element);
                  var Going = true;
                  ellipse.OnMouseUp = (ellipse0, y0) => {
                    y0.Handled = true;
                    Refresh();
                    Going = false;
                    Mouse.Capture(ellipse0.CurrentValue, CaptureMode.None);
                    return false;
                  };
                  ellipse.OnMouseMove = (ellipse0, y0) => {
                    y0.Handled = true;
                    if (!Going) return false;
                    Refresh();
                    return true;
                  };
                  return true;
                },
              };


            };

            {
              var From = (CutStart + CutEnd) / 2d;
              var From3D = CutTransform.To3D(From.InsertZ(-1));
              var NewStart = ComputeEndPoint(From, CutStart, From3D.Z);
              var NewEnd = CutEndFor(CutSlope, NewStart);
              var Length = (NewEnd - NewStart).Length;

              NewStart = (Length < 0.1).Condition(CutStart, NewStart);
              var Refresh = Point4DBl.Assign(CutInfo,
                new Point4DBl(NewStart, CutSlope));
              MakeControl(From3D, Refresh, Brushes.Orange);
            }
            {
              Action Refresh;
              var From = CutStart;
              var From3D = CutTransform.To3D(From.InsertZ(-1));
              var NewStart = ComputeEndPoint(From, From, From3D.Z);
              var End1 = CutEnd;
              var NewSlope = (End1 - NewStart);

              var NewEnd = CutEndFor(NewSlope, NewStart);
              var Condition = (NewSlope.IsNaN.Any | NewSlope.LengthSquared == 0 | NewEnd.IsNaN.Any);
              NewSlope = Condition.Condition(CutSlope, NewSlope.Normalize);
              NewStart = Condition.Condition(CutStart, NewStart);

              Refresh = Point4DBl.Assign(CutInfo,
                new Point4DBl(NewStart, NewSlope));
              MakeControl(From3D, Refresh, Brushes.Blue);
            }
            {
              Action Refresh;
              var From = CutEnd;
              var From3D = CutTransform.To3D(From.InsertZ(-1));
              var NewStart = CutStart;
              var End1 = ComputeEndPoint(From, From, From3D.Z);
              var NewSlope = (End1 - NewStart);

              var NewEnd = CutEndFor(NewSlope, NewStart);
              var Condition = (NewSlope.IsNaN.Any | NewSlope.LengthSquared == 0 | NewEnd.IsNaN.Any);

              NewSlope = Condition.Condition(CutSlope, NewSlope.Normalize);
              NewStart = Condition.Condition(CutStart, NewStart);

              Refresh = Point4DBl.Assign(CutInfo, new Point4DBl(NewStart, NewSlope));
              MakeControl(From3D, Refresh, Brushes.Blue);
            }
          }
          var lbl0 = new LabelBl(Canvas) {
            RightTop = new PointBl(Canvas.Width, 0d),
            Content = "Start ".Bl() + CutStart,
          };
          var lbl1 = new LabelBl(Canvas) {
            RightTop = lbl0.RightBottom,
            Content = " Slope ".Bl() + CutSlope,
          };
          var lbl2 = new LabelBl(Canvas) {
            RightTop = lbl1.RightBottom,
            Content = " End ".Bl() + CutEnd,
          };
        }
*/
/*
Func<PointBl, DoubleBl, PointBl> G = (p, z) => {
  p = p / ViewportSize;
  p = (CutTransform).To2D(p.InsertZ(z));
  return p;
};

Func<PointBl, PointBl, DoubleBl, PointBl> ComputeEndPoint = (FromA, FromB, Z) => {
  var MousePosition = G(OverlayContent.MousePosition, Z);
  var Delta = MousePosition - FromA;
  var q = FromB + Delta;
  q = q.Saturate;
  q = 2d * q - 1d;
  var h = q.Abs;
  q = (h.X > h.Y).Condition(new PointBl(q.X.Sign, q.Y), new PointBl(q.X, q.Y.Sign));
  q = (q + 1d) / 2d;
  return q;
};

var From = (CutStart + CutEnd) / 2d;
var From3D = CutTransform.To3D(From.InsertZ(-1));
var NewStart = ComputeEndPoint(From, CutStart, From3D.Z);
var NewEnd = CutEndFor(CutSlope, NewStart);
var Length = (NewEnd - NewStart).Length;

NewStart = (Length < 0.1).Condition(CutStart, NewStart);
var Refresh = Point4DBl.Assign(CutInfo,
  new Point4DBl(NewStart, CutSlope));
*/
